﻿
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;


namespace MTP.Serialize
{
    class ServiceContext
    {
        private static readonly ConcurrentDictionary<string, ServiceContext> providerFactory = new ConcurrentDictionary<string, ServiceContext>();



        private const string SERVICE_KEY = "service";

        private const string ACTION_KEY = "action";

        private readonly MethodInfo[] methodInfos;

        private readonly Type serviceType;

        public ServiceContext(Type serviceType)
        {
            methodInfos = serviceType.GetMethods(BindingFlags.Public | BindingFlags.Instance);
            this.serviceType = serviceType;
        }

        public Stream Execute(IServiceProvider serviceProvider, Stream stream, Dictionary<string, string> header)
        {
            if (!header.TryGetValue(ACTION_KEY, out string actionName))
            {
                throw new ArgumentException(ACTION_KEY + " is required");
            }
          
            var byteSplit = MTPSerialize.DeserializeSplit(stream);

            object[] results = MTPCoreUtil.MethodsInvoke(serviceProvider.GetService(serviceType),
                methodInfos,
                actionName,
                parameters => ParseObjects(byteSplit, parameters));
            return MTPSerialize.SerializeStreamArray(results);
        }

        private static object[] ParseObjects(List<byte[]> byteSplit, ParameterInfo[] parameterInfos)
        {

            object[] parameters = new object[parameterInfos.Length];

            for (int i = 0; i < parameterInfos.Length; i++)
            {
                var parameter = parameterInfos[i];
                if (byteSplit.Count > i)
                {
                    if (!parameter.IsOut)
                    {
                        parameters[i] = MTPSerialize.Deserialize(byteSplit[i], parameter.ParameterType);
                    }
                }
                else
                {
                    parameters[i] = parameter.DefaultValue;
                }
            }
            return parameters;
        }

        public static ServiceContext CreateFromFactory(IDictionary<string, string> header, MtpOptions mtpOptions)
        {
            if (!header.TryGetValue(SERVICE_KEY, out string service))
            {
                throw new ArgumentException(SERVICE_KEY + " is required");
            }
            return providerFactory.GetOrAdd(service, serviceName =>
            {
                if (!mtpOptions.Services.TryGetValue(service, out Type serviceType))
                {
                    throw new NotSupportedException($"service: {service} notFound");
                }
                return new ServiceContext(serviceType);
            });
        }
    }
}
